from concurrent.futures import ProcessPoolExecutor
from collections.abc import Iterator
from itertools import islice
import uuid
from collections import deque

from meta import LogMixin, TimerMixin, MAX_PROCESS_NUM

class TokenID(Iterator):
    """令牌ID生成器"""

    def __init__(self, num=1):
        super().__init__()
        self.num = num
        self.now = 0

    def __next__(self):
        if self.now >= self.num:
            raise StopIteration
        self.now += 1
        return uuid.uuid4().int


class Tokens(LogMixin, TimerMixin):
    """代币池对象
    
    方法：

        gen(num)：向池子中生成代币

        split(num)：从池子中分出代币成为新的池子

        __iadd__(b:Tokens)：将b中的代币转移到当前池子中
            e.g.
                vault_a += vault_b

        __sub__(b:int)：将当前池子中的代币减去b个并返回
        为防止出现负数，只有当b>0时才会减去，否则不会减去且抛出ValueError异常
        如果想要总的代币数量不变，则必须将返回的代币堆赋给某个已经存在的代币堆
            e.g.
                vault_c = vault_a - 10

        __getitem__(item)：获取池子中的代币

        __len__()：获取池子中的代币数量

        __str__()：获取池子的字符串表示

        __iter__()：获取池子的迭代器
                
    属性：
        token_list：代币列表，使用deque实现
    
    """

    def __init__(self, token: deque = None) -> None:
        """初始化代币池
        
        token: deque类型的代币id列表，如果不提供，则初始化为空列表，可以通过gen()方法生成代币
        
        """
        super().__init__()

        if token is None:
            self.token_list = deque()
        else:
            self.token_list = token

    def gen(self, num: int = 1):
        """多进程生成代币

        num: 代币数量

        """
        self.timer.mark(f'多进程生成代币{num}个')  # 带有计时器的打点
        
        # 计算CPU核心数的整数倍个多进程
        _process_chunk_size = num // MAX_PROCESS_NUM
        _total_times = _process_chunk_size
        _left_times = num % MAX_PROCESS_NUM

        # _total_times为0时，不需要多进程
        if _total_times == 0:
            self.add_tokens(token_ids=TokenID(num=num))
            return
        else:
            # 多进程池
            with ProcessPoolExecutor(max_workers=MAX_PROCESS_NUM) as excutor:
                
                # 开始多进程处理(由于参数为迭代器且为性能主要开销，故不使用map)
                futures = []

                # 多进程计算token的id，结果保存在futures中
                for _ in range(MAX_PROCESS_NUM):
                    future = excutor.submit(
                        self.add_tokens, TokenID(num=_total_times))
                    futures.append(future)

            # 阻塞式等待并整合结果
            for _ in futures:
                self += _.result()  # result()方法阻塞式等待结果

            if _left_times != 0:  # 最后几个，进程数量必定小于CPU数量，此时使用多进程进程切换开销必定大于单进程处理本身
                self.add_tokens(TokenID(num=_left_times))
            
            # 多进程完成

        self.timer.log()

    def split(self, num=1):
        """分出另一个代币堆"""
        return self - num

    def __clear(self):
        """清空代币堆"""
        self.token_list.clear()

    def __iadd__(self, b: 'Tokens'):
        """加上其他的代币堆
        
        给自身添加另一个代币堆的代币
        之后清空另一个代币堆

        返回自身
        
        """
        self.token_list.extend(b.token_list)
        b.__clear()
        return self

    def __sub__(self, b: int):
        """分出另一个代币堆

        返回：
            另一个代币堆

        """

        if b > len(self):
            raise ValueError("分出的数量不能大于代币池中的数量")
            return Tokens()

        self.timer.mark('切片代币堆')

        # 使用高性能islice()函数，deque不支持切片
        # 100万切片时间为0.15s
        deque_b = deque(islice(self.token_list, b))  # 等价于list[:num]
        self.token_list = deque(islice(self.token_list, b, None))  # 等价于list[num:]

        # # 使用for遍历 + pop()函数
        # # 100万切片时间为0.81s
        # # 使用python的for循环，远远没有纯C语言实现的islice()快
        # deque_b = deque(self.token_list.pop() for _ in range(num))

        self.timer.log()
        return Tokens(deque_b)

    def __getitem__(self, item):
        return self.token_list[item]

    def __len__(self):
        return len(self.token_list)

    def __str__(self) -> str:
        return f"代币池{id(self)}({len(self.token_list)})"

    def __iter__(self):
        return self.token_list.__iter__()

    def add_tokens(self, token_ids: TokenID = TokenID(num=1)):
        """添加新的token

        参数：
            TokenID生成器

        返回：
            self，便于多进程传参

        """
        for id in token_ids:
            self.token_list.append(id)
        return self

    @staticmethod
    def new_token_id(num=1):
        for i in range(num):
            yield uuid.uuid4().int
